#!/system/bin/sh
# Validate settings.ini
if ! sh -n /data/adb/box/settings.ini 2>"/data/adb/box/run/settings_err.log"; then
  echo "err: settings.ini contains a syntax error" | tee -a "/data/adb/box/run/settings_err.log"
  exit 1
fi

scripts_dir="${0%/*}"
source /data/adb/box/settings.ini

fwmark="16777216/16777216"
table="2024"
pref="100"
# disable or enable QUIC using iptables rules. Note that this may cause some websites to become inaccessible.
quic="enable"
clash_dns_forward="enable"
fake_ip_range=""

# ex: 7.1.1
buildVersion=$(getprop ro.build.version.release)
minBuildVersion="11"
IPV="iptables" # Default
IP6V="ip6tables" # Default
# ex: 7.1.1 -> 7
buildVersionMajor=${buildVersion%%.*}
if [ "$buildVersionMajor" -ge "$minBuildVersion" ]; then
  IPV="iptables -w 100"
  IP6V="ip6tables -w 100"
fi

case "${bin_name}" in
  "clash")
    clash_mode=$(busybox awk '!/^ *#/ && /mode: / { print $2;found=1; exit } END{ if(!found) print "rules" }' "${clash_config}" 2>/dev/null)

    clash_enhanced_mode=$(busybox awk '!/^ *#/ && /enhanced-mode: / { print $2;found=1; exit } END{ if(!found) print "fake-ip" }' "${clash_config}" 2>/dev/null)

    fake_ip_range=$(busybox awk '!/^ *#/ && /fake-ip-range:/ { print $2; found=1; exit } END { if (!found) print "198.18.0.1/16" }' "${clash_config}" 2>/dev/null)

    clash_dns_port=$(sed -n '/^dns:/,/^[^ ]/p' "${clash_config}" | grep -E '^[^#]*listen:.*:[0-9]+' | grep -Eo '[0-9]+' | tail -n 1)
    clash_dns_port=${clash_dns_port:-1053}

    if [[ "${network_mode}" == @(mixed|tun) ]]; then
      tun_device=$(busybox awk '!/^ *#/ && /device: / { print $2;found=1; exit } END{ if(!found) print "meta" }' "${clash_config}" 2>/dev/null)
    fi
    ;;
  "sing-box")
    if [[ "${network_mode}" == @(mixed|tun) ]]; then
      tun_device=$(find "${box_dir}/sing-box/" -maxdepth 1 -type f -name "*.json" -exec busybox grep -oE '"interface_name": "[^"]*' {} + | busybox awk -F'"' '{print $4}' 2>/dev/null | head -n 1)

      if [ -z "$tun_device" ]; then
        tun_device="tun0"
      fi
    fi

    fake_ip_range=$(find ${box_dir}/sing-box/ -maxdepth 1 -type f -name "*.json" -exec busybox awk -F'"' '/inet4_range/ {print $4}' {} +)
    fake_ip6_range=$(find ${box_dir}/sing-box/ -maxdepth 1 -type f -name "*.json" -exec busybox awk -F'"' '/inet6_range/ {print $4}' {} +)
    ;;
  "hysteria")
    case "${network_mode}" in
      redirect|tproxy|enhance)
        true # do nothing
        ;;
      *)
        log Warning "$bin_name does not support network_mode: $network_mode, return to TProxy"
        sed -i 's/\(network_mode=\)\"[^\"]*\"/\1"tproxy"/g' ${settings}
        ;;
    esac
    ;;
  "xray" | "v2fly")
    if [[ "${network_mode}" != "tproxy" ]]; then
      log Warning "$bin_name does not support network_mode: $network_mode, return to TProxy"
      sed -i 's/\(network_mode=\)\"[^\"]*\"/\1"tproxy"/g' ${settings}
    fi
    ;;
  *)
    log Error "<${bin_name}> unknown binary."
    exit 1
    ;;
esac

box_etc() {
  case "${bin_name}" in
    clash)
      log Debug "enhanced-mode: $clash_enhanced_mode, fake-ip-range: $fake_ip_range, listen-port: $clash_dns_port, mode: $clash_mode"
      ;;
    sing-box)
      if [ -n "${fake_ip_range}" ] && [ "${bin_name}" = "sing-box" ]; then
        log Debug "fake-ip-range: ${fake_ip_range}, ${fake_ip6_range}"
      fi
      ;;
    *) 
      true 
      ;;
  esac
  if [[ "${network_mode}" == @(mixed|tun) ]]; then
    log Info "tun device: ($tun_device)"
  fi
}

bin_alive() {
  local PID=$(<"${box_pid}" 2>/dev/null)
  if ! kill -0 "$PID" >/dev/null; then
    log Error "$(<"${box_run}/${bin_name}.log")"
    log Error "${bin_name} service is not running."
    log Error "please check ${bin_name}.log for more information."
    log Error "killing stale pid $PID"
    for bin in "${bin_list[@]}"; do
      killall -15 "${bin}" >/dev/null 2>&1 || busybox pkill -15 "${bin}" >/dev/null 2>&1
    done
    cleanup_iptables
    [ -f "${box_pid}" ] && rm -f "${box_pid}"
    return 1
  else
    return 0
  fi
}

find_packages_uid() {
  echo -n "" > "${uid_list}"

  for package in "${packages_list[@]}"; do
    if [[ "$package" == *:* ]]; then
      user="${package%%:*}"
      pkg="${package##*:}"
    else
      user=0
      pkg="$package"
    fi

    appid="$(busybox awk -v p="$pkg" '$1 == p {print $2}' "$system_packages_file")"

    if [[ -n "$appid" ]]; then
      uid=$((user * 100000 + appid))
      echo "$uid" >> "${uid_list}"
    fi
  done
}

probe_user_group() {
  if PID=$(busybox pidof ${bin_name}) ; then
    box_user=$(stat -c %U /proc/$PID)
    box_group=$(stat -c %G /proc/$PID)
    return 0
  else
    IFS=':' read -r box_user box_group <<< "${box_user_group}"
    return 1
  fi
}

disable_ipv6() {
  sysctl -w net.ipv4.ip_forward=1
  sysctl -w net.ipv6.conf.all.forwarding=0

  sysctl -w net.ipv6.conf.all.accept_ra=0
  sysctl -w net.ipv6.conf.wlan0.accept_ra=0
  sysctl -w net.ipv6.conf.all.disable_ipv6=1
  sysctl -w net.ipv6.conf.default.disable_ipv6=1
  sysctl -w net.ipv6.conf.wlan0.disable_ipv6=1

  # add: block Askes ipv6 completely
  ip -6 rule add unreachable pref "${pref}"
} >/dev/null 2>&1

ipv6_enable() {
  sysctl -w net.ipv4.ip_forward=1
  sysctl -w net.ipv6.conf.all.forwarding=1

  sysctl -w net.ipv6.conf.all.accept_ra=2
  sysctl -w net.ipv6.conf.wlan0.accept_ra=2
  sysctl -w net.ipv6.conf.all.disable_ipv6=0
  sysctl -w net.ipv6.conf.default.disable_ipv6=0
  sysctl -w net.ipv6.conf.wlan0.disable_ipv6=0

  # del: block Askes ipv6 completely
  ip -6 rule del unreachable pref "${pref}"

  # add: blocks all outgoing IPv6 traffic using the UDP protocol to port 53, effectively preventing DNS queries over IPv6.
  $IP6V -A OUTPUT -p udp --destination-port 53 -j DROP
} >/dev/null 2>&1

intranet=(
  0.0.0.0/8
  10.0.0.0/8
  100.64.0.0/10
  127.0.0.0/8
  169.254.0.0/16
  172.16.0.0/12
  192.0.0.0/24
  192.0.2.0/24
  192.88.99.0/24
  192.168.0.0/16
  198.51.100.0/24
  203.0.113.0/24
  224.0.0.0/4
  240.0.0.0/4
  255.0.0.0/4
  255.255.255.0/24
  255.255.255.255/32
)
# The use of 100.0.0.0/8 instead of 100.64.0.0/10 is purely due to a mistake by China Telecom's service provider, and you can change it back.
intranet+=($(ip -4 a | busybox awk '/inet/ {print $2}' | busybox grep -vE "^127.0.0.1"))

intranet6=(
  ::/128
  ::1/128
  ::ffff:0:0/96
  100::/64
  64:ff9b::/96
  2001::/32
  2001:10::/28
  2001:20::/28
  2001:db8::/32
  2002::/16
  fc00::/7
  fe80::/10
  ff00::/8
)
intranet6+=($(ip -6 a | busybox awk '/inet6/ {print $2}' | busybox grep -vE "^fe80|^::1|^fd00"))

probe_tun_device() {
  busybox ifconfig | grep -q "${tun_device}" || return 1
}

forward() {
  local action=$1

  # ${iptables} -t nat ${action} POSTROUTING -o ${tun_device} -j MASQUERADE
  ${iptables} "${action}" FORWARD -i "${tun_device}" -j ACCEPT
  ${iptables} "${action}" FORWARD -o "${tun_device}" -j ACCEPT

  sysctl -w net.ipv4.ip_forward=1
  sysctl -w net.ipv4.conf.default.rp_filter=2
  sysctl -w net.ipv4.conf.all.rp_filter=2

} >/dev/null 2>&1

start_redirect() {
  if [ "${iptables}" = "$IPV" ]; then
    ${iptables} -t nat -N BOX_EXTERNAL
    ${iptables} -t nat -F BOX_EXTERNAL
    ${iptables} -t nat -N BOX_LOCAL
    ${iptables} -t nat -F BOX_LOCAL
  fi

  if [ "${iptables}" = "$IPV" ]; then
    if [ "${bin_name}" = "clash" ]; then
      ${iptables} -t nat -A BOX_EXTERNAL -p udp --dport 53 -j REDIRECT --to-ports "${clash_dns_port}"
      ${iptables} -t nat -A BOX_LOCAL -p udp --dport 53 -j REDIRECT --to-ports "${clash_dns_port}"
    # else
      #  Other types of inbound should be added here to receive DNS traffic instead of sniffing
      # ${iptables} -t nat -A BOX_EXTERNAL -p udp --dport 53 -j REDIRECT --to-ports "${redir_port}"
      # ${iptables} -t nat -A BOX_LOCAL -p udp --dport 53 -j REDIRECT --to-ports "${redir_port}"
    fi

    # Fix ICMP (ping). This does not guarantee that the ping result is valid. Just that it returns a result
    # if [[ "${bin_name}" == @(clash|sing-box) ]]; then
      # if [ -n "${fake_ip_range}" ]; then
        # ${iptables} -t nat -A BOX_EXTERNAL -d "${fake_ip_range}" -p icmp -j DNAT --to-destination 127.0.0.1
        # ${iptables} -t nat -A BOX_LOCAL -d "${fake_ip_range}" -p icmp -j DNAT --to-destination 127.0.0.1
      # fi
    # fi

    ${iptables} -t nat -N LOCAL_IP_V4
    ${iptables} -t nat -F LOCAL_IP_V4
    
    for subnet in ${intranet[@]} ; do
      ${iptables} -t nat -A BOX_EXTERNAL -d ${subnet} -j RETURN
      ${iptables} -t nat -A BOX_LOCAL -d ${subnet} -j RETURN
    done
    
    ${iptables} -t nat -A BOX_EXTERNAL -j LOCAL_IP_V4
    ${iptables} -t nat -A BOX_LOCAL -j LOCAL_IP_V4

    ${iptables} -t nat -A BOX_EXTERNAL -p tcp -i lo -j REDIRECT --to-ports "${redir_port}"

    if [ "${ap_list}" != "" ]; then
      for ap in "${ap_list[@]}"; do
        ${iptables} -t nat -A BOX_EXTERNAL -p tcp -i "${ap}" -j REDIRECT --to-ports "${redir_port}"
      done
        [ ${network_mode} = "enhance" ] || log Info "${ap_list[*]} transparent proxy."
    fi

    ${iptables} -t nat -I PREROUTING -j BOX_EXTERNAL
    ${iptables} -t nat -I BOX_LOCAL -m owner --uid-owner "${box_user}" --gid-owner "${box_group}" -j RETURN

    if [ "${ignore_out_list}" != "" ]; then
      for ignore in "${ignore_out_list[@]}"; do
        ${iptables} -t nat -A BOX_LOCAL -o "${ignore}" -j RETURN
      done
      [ ${network_mode} = "enhance" ] || log Info "${ignore_out_list[*]} ignore transparent proxy."
    fi
  fi

  if [ "${iptables}" = "$IPV" ]; then
    case "${proxy_mode}" in
      blacklist|black)
        if [ -z "$(cat "${uid_list[@]}")" ] ; then
          ${iptables} -t nat -A BOX_LOCAL -p tcp -j REDIRECT --to-ports "${redir_port}"
          [ ${network_mode} = "enhance" ] || log Info "Transparent proxy for all apps."
        else
          while read -r appid; do
            ${iptables} -t nat -A BOX_LOCAL -m owner --uid-owner "${appid}" -j RETURN
          done < "${uid_list[@]}"
          ${iptables} -t nat -A BOX_LOCAL -p tcp -j REDIRECT --to-ports "${redir_port}"
          [ ${network_mode} = "enhance" ] || log Info "proxy mode: ${proxy_mode} (${packages_list[*]}) no transparent proxy."
        fi

        if [ "${gid_list}" != "" ] ; then
          for gid in ${gid_list[@]} ; do
            ${iptables} -t nat -A BOX_LOCAL -m owner --gid-owner ${gid} -j RETURN
          done
          [ ${network_mode} = "enhance" ] || {
            [ "${iptables}" = "$IPV" ] &&  log Info "proxy mode: ${proxy_mode}, GID (${gid_list[*]}) no transparent proxy."
          }
        fi
        ;;
      whitelist|white)
        if [ -z "$(cat "${uid_list[@]}")" ] ; then
          ${iptables} -t nat -A BOX_LOCAL -p tcp -j REDIRECT --to-ports "${redir_port}"
          [ ${network_mode} = "enhance" ] || log Info "Transparent proxy for all apps."
        else
          while read -r appid; do
            ${iptables} -t nat -A BOX_LOCAL -p tcp -m owner --uid-owner "${appid}" -j REDIRECT --to-ports "${redir_port}"
          done < "${uid_list[@]}"
          ${iptables} -t nat -A BOX_LOCAL -p tcp -m owner --uid-owner 0 -j REDIRECT --to-ports "${redir_port}"
          ${iptables} -t nat -A BOX_LOCAL -p tcp -m owner --uid-owner 1052 -j REDIRECT --to-ports "${redir_port}"
          [ ${network_mode} = "enhance" ] || log Info "proxy mode: ${proxy_mode} (${packages_list[*]}) transparent proxy."
        fi

        if [ "${gid_list}" != "" ] ; then
          for gid in ${gid_list[@]} ; do
            ${iptables} -t nat -A BOX_LOCAL -p tcp -m owner --gid-owner ${gid} -j REDIRECT --to-ports ${redir_port}
          done
          [ ${network_mode} = "enhance" ] || [ "${iptables}" = "$IPV" ] && log Info "proxy mode: ${proxy_mode}, GID (${gid_list[*]}) transparent proxy."
        fi
        ;;
      *)
        log Warning "proxy mode: ${proxy_mode} < error."
        ${iptables} -t nat -A BOX_LOCAL -p tcp -j REDIRECT --to-ports "${redir_port}"
        [ ${network_mode} = "enhance" ] || log Info "Transparent proxy for all apps."
        ;;
    esac
  fi

  if [ "${iptables}" = "$IPV" ]; then
    ${iptables} -t nat -I OUTPUT -j BOX_LOCAL
  fi

  if [ "${iptables}" = "$IPV" ]; then
    ${iptables} -A OUTPUT -d 127.0.0.1 -p tcp -m owner --uid-owner "${box_user}" --gid-owner "${box_group}" -m tcp --dport "${redir_port}" -j REJECT
  else
    ${iptables} -A OUTPUT -d ::1 -p tcp -m owner --uid-owner "${box_user}" --gid-owner "${box_group}" -m tcp --dport "${redir_port}" -j REJECT
  fi
}

stop_redirect() {
  if [ "${iptables}" = "$IPV" ]; then
    ${iptables} -t nat -D PREROUTING -j BOX_EXTERNAL
    ${iptables} -t nat -D OUTPUT -j BOX_LOCAL
  fi

  if [ "${iptables}" = "$IPV" ]; then
    ${iptables} -D OUTPUT -d 127.0.0.1 -p tcp -m owner --uid-owner "${box_user}" --gid-owner "${box_group}" -m tcp --dport "${redir_port}" -j REJECT
    ${iptables} -D OUTPUT -d 127.0.0.1 -p tcp -m owner --uid-owner 0:3005 -m tcp --dport "${redir_port}" -j REJECT
  else
    ${iptables} -D OUTPUT -d ::1 -p tcp -m owner --uid-owner "${box_user}" --gid-owner "${box_group}" -m tcp --dport "${redir_port}" -j REJECT
    ${iptables} -D OUTPUT -d ::1 -p tcp -m owner --uid-owner 0:3005 -m tcp --dport "${redir_port}" -j REJECT
  fi

  if [ "${iptables}" = "$IPV" ]; then
    # ${iptables} -t nat -D BOX_EXTERNAL -d "${fake_ip_range}" -p icmp -j DNAT --to-destination 127.0.0.1
    # ${iptables} -t nat -D BOX_LOCAL -d "${fake_ip_range}" -p icmp -j DNAT --to-destination 127.0.0.1
    ${iptables} -t nat -F BOX_EXTERNAL
    ${iptables} -t nat -X BOX_EXTERNAL
    ${iptables} -t nat -F BOX_LOCAL
    ${iptables} -t nat -X BOX_LOCAL
    ${iptables} -t nat -F LOCAL_IP_V4
    ${iptables} -t nat -X LOCAL_IP_V4
  fi
}

start_tproxy() {
  if [ "${iptables}" = "$IPV" ]; then
    ip rule add fwmark "${fwmark}" table "${table}" pref "${pref}"
    ip route add local default dev lo table "${table}"
  else
    ip -6 rule add fwmark "${fwmark}" table "${table}" pref "${pref}"
    ip -6 route add local default dev lo table "${table}"
  fi

  ${iptables} -t mangle -N BOX_EXTERNAL >/dev/null 2>&1
  ${iptables} -t mangle -F BOX_EXTERNAL >/dev/null 2>&1

  # ${iptables} -t mangle -A BOX_EXTERNAL -m mark --mark ${routing_mark} -j RETURN

  # Bypass other if, notice: Some interface is named with r_ / oem / nm_ / qcom_, it might need more complicated solution.
  # ${iptables} -t mangle -I BOX_EXTERNAL -i rmnet_data+ -j RETURN
  # ${iptables} -t mangle -I BOX_EXTERNAL -i ccmni+ -j RETURN

  if [ "${clash_dns_forward}" = "enable" ] && [[ "${bin_name}" == @(clash|hysteria) ]] ; then
      [ ${network_mode} = "enhance" ] || ${iptables} -t mangle -A BOX_EXTERNAL -p tcp --dport 53 -j RETURN
      ${iptables} -t mangle -A BOX_EXTERNAL -p udp --dport 53 -j RETURN
  else
      [ ${network_mode} = "enhance" ] || ${iptables} -t mangle -A BOX_EXTERNAL -p tcp --dport 53 -j TPROXY --on-port ${tproxy_port} --tproxy-mark ${fwmark}
      ${iptables} -t mangle -A BOX_EXTERNAL -p udp --dport 53 -j TPROXY --on-port ${tproxy_port} --tproxy-mark ${fwmark}
  fi

  # Skip traffic already handled by TProxy
  # If the interface of the default route has a public IPv4 or IPv6 address assigned by the ISP, omitting these rules will result in abnormal proxy behavior for local traffic'
  # [ ${network_mode} = "enhance" ] || ${iptables} -t mangle -A BOX_EXTERNAL -p tcp -m socket --transparent -j MARK --set-xmark ${fwmark}
  # ${iptables} -t mangle -A BOX_EXTERNAL -p udp -m socket --transparent -j MARK --set-xmark ${fwmark}
  # ${iptables} -t mangle -A BOX_EXTERNAL -m socket -j RETURN

  # Bypass intranet, run `su -c 'zcat /proc/config.gz | grep -i addrtype'` to check compatibility
  # ${iptables} -t mangle -A BOX_EXTERNAL -m addrtype --dst-type LOCAL -j RETURN
  if [ "${iptables}" = "$IPV" ]; then
    for subnet in ${intranet[@]} ; do
      ${iptables} -t mangle -A BOX_EXTERNAL -d ${subnet} -j RETURN
    done
    ${iptables} -t mangle -N LOCAL_IP_V4
    ${iptables} -t mangle -F LOCAL_IP_V4
    ${iptables} -t mangle -A BOX_EXTERNAL -j LOCAL_IP_V4
  else
    for subnet6 in ${intranet6[@]} ; do
      ${iptables} -t mangle -A BOX_EXTERNAL -d ${subnet6} -j RETURN
    done
    ${iptables} -t mangle -N LOCAL_IP_V6
    ${iptables} -t mangle -F LOCAL_IP_V6
    ${iptables} -t mangle -A BOX_EXTERNAL -j LOCAL_IP_V6
  fi

  [ ${network_mode} = "enhance" ] || ${iptables} -t mangle -A BOX_EXTERNAL -p tcp -i lo -j TPROXY --on-port "${tproxy_port}" --tproxy-mark "${fwmark}"
  ${iptables} -t mangle -A BOX_EXTERNAL -p udp -i lo -j TPROXY --on-port "${tproxy_port}" --tproxy-mark "${fwmark}"

  # Allow ap interface, Notice: Old android device may only have one wlan interface. Some new android device have multiple wlan interface like wlan0(for internet), wlan1(for AP), loop through the access point list
  if [ "${ap_list}" != "" ]; then
    for ap in ${ap_list[@]} ; do
      [ ${network_mode} = "enhance" ] || ${iptables} -t mangle -A BOX_EXTERNAL -p tcp -i "${ap}" -j TPROXY --on-port "${tproxy_port}" --tproxy-mark "${fwmark}"
      ${iptables} -t mangle -A BOX_EXTERNAL -p udp -i "${ap}" -j TPROXY --on-port "${tproxy_port}" --tproxy-mark "${fwmark}"
    done
    [ "${iptables}" = "$IPV" ] && log Info "${ap_list[*]} transparent proxy."
  fi

  ${iptables} -t mangle -I PREROUTING -j BOX_EXTERNAL
  ${iptables} -t mangle -N BOX_LOCAL
  ${iptables} -t mangle -F BOX_LOCAL

  ${iptables} -t mangle -A BOX_LOCAL -m owner --uid-owner ${box_user} --gid-owner ${box_group} -j RETURN
  # ${iptables} -t mangle -A BOX_LOCAL -m mark --mark ${routing_mark} -j RETURN

  if [ "${ignore_out_list}" != "" ]; then
    for ignore in ${ignore_out_list[@]} ; do
      ${iptables} -t mangle -A BOX_LOCAL -o "${ignore}" -j RETURN
    done
    [ "${iptables}" = "$IPV" ] && log Info "${ignore_out_list[*]} ignore transparent proxy."
  fi

  if [ "${clash_dns_forward}" = "enable" ] && [[ "${bin_name}" == @(clash|hysteria) ]] ; then
      [ ${network_mode} = "enhance" ] || ${iptables} -t mangle -A BOX_LOCAL -p tcp --dport 53 -j RETURN
      ${iptables} -t mangle -A BOX_LOCAL -p udp --dport 53 -j RETURN
  else
      [ ${network_mode} = "enhance" ] || ${iptables} -t mangle -A BOX_LOCAL -p tcp --dport 53 -j MARK --set-xmark ${fwmark}
      ${iptables} -t mangle -A BOX_LOCAL -p udp --dport 53 -j MARK --set-xmark ${fwmark}
  fi

  if [ "${iptables}" = "$IPV" ]; then
    for subnet in ${intranet[@]} ; do
      ${iptables} -t mangle -A BOX_LOCAL -d ${subnet} -j RETURN
    done
    ${iptables} -t mangle -A BOX_LOCAL -j LOCAL_IP_V4
  else
    for subnet6 in ${intranet6[@]} ; do
      ${iptables} -t mangle -A BOX_LOCAL -d ${subnet6} -j RETURN
    done
    ${iptables} -t mangle -A BOX_LOCAL -j LOCAL_IP_V6
  fi

  case "${proxy_mode}" in
    blacklist|black)
      if [ -z "$(cat "${uid_list[@]}")" ] ; then
        [ ${network_mode} = "enhance" ] || ${iptables} -t mangle -A BOX_LOCAL -p tcp -j MARK --set-xmark "${fwmark}"
        ${iptables} -t mangle -A BOX_LOCAL -p udp -j MARK --set-xmark "${fwmark}"
        [ "${iptables}" = "$IPV" ] && log Info "transparent proxy for all apps."
      else
        while read -r appid; do
          ${iptables} -t mangle -A BOX_LOCAL -m owner --uid-owner "${appid}" -j RETURN
        done < "${uid_list[@]}"
        [ ${network_mode} = "enhance" ] || ${iptables} -t mangle -A BOX_LOCAL -p tcp -j MARK --set-xmark "${fwmark}"
        ${iptables} -t mangle -A BOX_LOCAL -p udp -j MARK --set-xmark "${fwmark}"
        [ "${iptables}" = "$IPV" ] && log Info "proxy mode: ${proxy_mode} (${packages_list[*]}) no transparent proxy."
      fi

      if [ "${gid_list}" != "" ] ; then
        for gid in ${gid_list[@]} ; do
          ${iptables} -t mangle -A BOX_LOCAL -m owner --gid-owner ${gid} -j RETURN
        done
        [ "${iptables}" = "$IPV" ] && log Info "proxy mode: ${proxy_mode}, GID (${gid_list[*]}) no transparent proxy."
      fi
      ;;
    whitelist|white)
      if [ -z "$(cat "${uid_list[@]}")" ] ; then
        [ ${network_mode} = "enhance" ] || ${iptables} -t mangle -A BOX_LOCAL -p tcp -j MARK --set-xmark "${fwmark}"
        ${iptables} -t mangle -A BOX_LOCAL -p udp -j MARK --set-xmark "${fwmark}"
        [ "${iptables}" = "$IPV" ] && log Info "transparent proxy for all apps."
      else
        while read -r appid; do
          [ ${network_mode} = "enhance" ] || ${iptables} -t mangle -A BOX_LOCAL -p tcp -m owner --uid-owner "${appid}" -j MARK --set-xmark "${fwmark}"
          ${iptables} -t mangle -A BOX_LOCAL -p udp -m owner --uid-owner "${appid}" -j MARK --set-xmark "${fwmark}"
        done < "${uid_list[@]}"
        [ ${network_mode} = "enhance" ] || ${iptables} -t mangle -A BOX_LOCAL -p tcp -m owner --uid-owner 0 -j MARK --set-xmark "${fwmark}"
        ${iptables} -t mangle -A BOX_LOCAL -p udp -m owner --uid-owner 0 -j MARK --set-xmark "${fwmark}"
        [ ${network_mode} = "enhance" ] || ${iptables} -t mangle -A BOX_LOCAL -p tcp -m owner --uid-owner 1052 -j MARK --set-xmark "${fwmark}"
        ${iptables} -t mangle -A BOX_LOCAL -p udp -m owner --uid-owner 1052 -j MARK --set-xmark "${fwmark}"
        [ "${iptables}" = "$IPV" ] && log Info "proxy mode: ${proxy_mode} (${packages_list[*]}) transparent proxy."
      fi

      if [ "${gid_list}" != "" ] ; then
        for gid in ${gid_list[@]} ; do
          [ ${network_mode} = "enhance" ] || ${iptables} -t mangle -A BOX_LOCAL -p tcp -m owner --gid-owner ${gid} -j MARK --set-xmark "${fwmark}"
          ${iptables} -t mangle -A BOX_LOCAL -p udp -m owner --gid-owner ${gid} -j MARK --set-xmark "${fwmark}"
        done
        [ "${iptables}" = "$IPV" ] && log Info "proxy mode: ${proxy_mode}, GID (${gid_list[*]}) transparent proxy."
      fi
      ;;
    *)
      log Debug "proxy mode: ${proxy_mode} < error"
      [ ${network_mode} = "enhance" ] || ${iptables} -t mangle -A BOX_LOCAL -p tcp -j MARK --set-xmark "${fwmark}"
      ${iptables} -t mangle -A BOX_LOCAL -p udp -j MARK --set-xmark "${fwmark}"
      [ "${iptables}" = "$IPV" ] && log Info "transparent proxy for all apps."
      ;;
    esac

  ${iptables} -t mangle -I OUTPUT -j BOX_LOCAL

  ${iptables} -t mangle -N DIVERT
  ${iptables} -t mangle -F DIVERT
  ${iptables} -t mangle -A DIVERT -j MARK --set-xmark "${fwmark}"
  ${iptables} -t mangle -A DIVERT -j ACCEPT
  [ ${network_mode} = "enhance" ] || ${iptables} -t mangle -I PREROUTING -p tcp -m socket -j DIVERT

  # Disable QUIC
  if [ "${quic}" = "disable" ]; then
    ${iptables} -A OUTPUT -p udp --dport 443 -j REJECT
    ${iptables} -A OUTPUT -p udp --dport 80 -j REJECT
    # ${iptables} -A OUTPUT -p udp -m multiport --dport 443,80 -j REJECT
    [ "${iptables}" = "$IPV" ] && log Warning "Disabling QUIC"
  fi

if [ ${network_mode} != "enhance" ]; then
  # This rule blocks local access to tproxy-port to prevent traffic loopback.
  if [ "${iptables}" = "$IPV" ]; then
    ${iptables} -A OUTPUT -d 127.0.0.1 -p tcp -m owner --uid-owner "${box_user}" --gid-owner "${box_group}" -m tcp --dport "${tproxy_port}" -j REJECT
  else
    ${iptables} -A OUTPUT -d ::1 -p tcp -m owner --uid-owner "${box_user}" --gid-owner "${box_group}" -m tcp --dport "${tproxy_port}" -j REJECT
  fi
fi

  if [ "${iptables}" = "$IPV" ]; then
    if [ "${clash_dns_forward}" = "enable" ] && [ "${bin_name}" = "clash" ]; then
      ${iptables} -t nat -N CLASH_DNS_EXTERNAL
      ${iptables} -t nat -F CLASH_DNS_EXTERNAL
      ${iptables} -t nat -A CLASH_DNS_EXTERNAL -p udp --dport 53 -j REDIRECT --to-ports "${clash_dns_port}"
      ${iptables} -t nat -I PREROUTING -j CLASH_DNS_EXTERNAL

      ${iptables} -t nat -N CLASH_DNS_LOCAL
      ${iptables} -t nat -F CLASH_DNS_LOCAL
      ${iptables} -t nat -A CLASH_DNS_LOCAL -m owner --uid-owner "${box_user}" --gid-owner "${box_group}" -j RETURN
      ${iptables} -t nat -A CLASH_DNS_LOCAL -p udp --dport 53 -j REDIRECT --to-ports "${clash_dns_port}"
      ${iptables} -t nat -I OUTPUT -j CLASH_DNS_LOCAL
    fi

    # Fix ICMP (ping), this does not guarantee that the ping result is valid (proxies such as clash do not support forwarding ICMP),just that it returns a result, "--to-destination" can be set to a reachable address.
    if [[ "${bin_name}" == @(clash|sing-box) ]]; then
      if [ -n "${fake_ip_range}" ]; then
        ${iptables} -t nat -I OUTPUT -d "${fake_ip_range}" -p icmp -j DNAT --to-destination 127.0.0.1
        ${iptables} -t nat -I PREROUTING -d "${fake_ip_range}" -p icmp -j DNAT --to-destination 127.0.0.1
      fi
    fi
  fi
}

stop_tproxy() {
  if [ "${iptables}" = "$IPV" ]; then
    ip rule del fwmark "${fwmark}" table "${table}" pref "${pref}"
    ip route del local default dev lo table "${table}"
    ip route flush table "${table}"
    ip rule del pref "${pref}"
  else
    ip -6 rule del fwmark "${fwmark}" table "${table}" pref "${pref}"
    ip -6 route del local default dev lo table "${table}"
    ip -6 route flush table "${table}"
    ip -6 rule del pref "${pref}"
  fi

  ${iptables} -t mangle -D PREROUTING -j BOX_EXTERNAL
  ${iptables} -t mangle -D PREROUTING -p tcp -m socket -j DIVERT

  ${iptables} -t mangle -D OUTPUT -j BOX_LOCAL

  ${iptables} -t mangle -D BOX_EXTERNAL -i rmnet_data+ -j RETURN
  ${iptables} -t mangle -D BOX_EXTERNAL -i ccmni+ -j RETURN

  ${iptables} -t mangle -F BOX_EXTERNAL
  ${iptables} -t mangle -X BOX_EXTERNAL

  ${iptables} -t mangle -F BOX_LOCAL
  ${iptables} -t mangle -X BOX_LOCAL

  ${IPV} -t mangle -F LOCAL_IP_V4
  ${IPV} -t mangle -X LOCAL_IP_V4
  ${IP6V} -t mangle -F LOCAL_IP_V6
  ${IP6V} -t mangle -X LOCAL_IP_V6

  ${iptables} -t mangle -F DIVERT
  ${iptables} -t mangle -X DIVERT

  # flush QUIC
  ${iptables} -D OUTPUT -p udp -m multiport --dport 443,80 -j REJECT
  ${iptables} -D OUTPUT -p udp --dport 443 -j REJECT
  ${iptables} -D OUTPUT -p udp --dport 80 -j REJECT

  if [ "${iptables}" = "$IPV" ]; then
    ${iptables} -D OUTPUT -d 127.0.0.1 -p tcp -m owner --uid-owner "${box_user}" --gid-owner "${box_group}" -m tcp --dport "${tproxy_port}" -j REJECT
    ${iptables} -D OUTPUT -d 127.0.0.1 -p tcp -m owner --uid-owner 0 --gid-owner 3005 -m tcp --dport "${tproxy_port}" -j REJECT
  else
    ${iptables} -D OUTPUT -d ::1 -p tcp -m owner --uid-owner "${box_user}" --gid-owner "${box_group}" -m tcp --dport "${tproxy_port}" -j REJECT
    ${iptables} -D OUTPUT -d ::1 -p tcp -m owner --uid-owner 0 --gid-owner 3005 -m tcp --dport "${tproxy_port}" -j REJECT
  fi

  if [ "${iptables}" = "$IPV" ]; then
    ${iptables} -t nat -D PREROUTING -j CLASH_DNS_EXTERNAL
    ${iptables} -t nat -D OUTPUT -j CLASH_DNS_LOCAL
    ${iptables} -t nat -F CLASH_DNS_EXTERNAL
    ${iptables} -t nat -X CLASH_DNS_EXTERNAL
    ${iptables} -t nat -F CLASH_DNS_LOCAL
    ${iptables} -t nat -X CLASH_DNS_LOCAL

    if [ -n "${fake_ip_range}" ]; then
      ${iptables} -t nat -D OUTPUT -p icmp -d "${fake_ip_range}" -j DNAT --to-destination 127.0.0.1
      ${iptables} -t nat -D PREROUTING -p icmp -d "${fake_ip_range}" -j DNAT --to-destination 127.0.0.1
      ${iptables} -t nat -D OUTPUT -d "${fake_ip_range}" -p icmp -j DNAT --to-destination 127.0.0.1
      ${iptables} -t nat -D PREROUTING -d "${fake_ip_range}" -p icmp -j DNAT --to-destination 127.0.0.1
    fi
  fi
}

cleanup_iptables() {
  for iptables in "$IPV" "$IP6V"; do
    iptables="${iptables}" && {
      stop_redirect
      stop_tproxy
      forward -D
    } >/dev/null 2>&1

    if [ "${iptables}" = "$IP6V" ]; then
      ${iptables} -D OUTPUT -p udp --destination-port 53 -j DROP >/dev/null 2>&1
    fi
  done
}

if [[ "${network_mode}" == @(redirect|mixed|tproxy|enhance) ]]; then
  case "$1" in
    enable|renew)
      box_etc
      log Info "$IPV + $IP6V"
      probe_user_group || {
        log Error "Failed to check BOX user group. Please ensure ${bin_name} kernel is started."
      }

      find_packages_uid
      cleanup_iptables

      [ $1 = "renew" ] && log Warning "cleaning up iptables transparent proxy rules."

      case "${network_mode}" in
        tproxy)
          log Info "Using Tproxy: tcp + udp."
          log Info "Creating iptables transparent proxy rules."

          iptables="$IPV"
          if start_tproxy; then
            log Info "Creating iptables transparent proxy rules done."
          else
            log Error "Creating iptables transparent proxy rules failed."
            stop_tproxy >/dev/null 2>&1
          fi

          if [ "${ipv6}" = "true" ]; then
            log Debug "Using IPv6."
            ipv6_enable
            iptables="$IP6V"

            if start_tproxy; then
              log Info "Creating ip6tables transparent proxy rules done."
            else
              log Error "Creating ip6tables transparent proxy rules failed."
              stop_tproxy >/dev/null 2>&1
            fi
          else
            disable_ipv6
            log Warning "Disabling IPv6."
          fi
        ;;
        redirect)
          log Info "Using Redirect: tcp + udp (direct)."
          log Info "Creating iptables transparent proxy rules."

          iptables="$IPV"
          if start_redirect; then
            log Info "Creating iptables transparent proxy rules done."
          else
            log Error "Creating iptables transparent proxy rule failed."
            stop_redirect >/dev/null 2>&1
          fi

          if [ "${ipv6}" = "true" ]; then
            log Debug "Using IPv6."
            ipv6_enable
            iptables="$IP6V"

            if start_redirect; then
              log Info "Creating ip6tables transparent proxy rules done."
            else
              log Error "Creating ip6tables transparent proxy rule failed."
              stop_redirect >/dev/null 2>&1
            fi
          else
            disable_ipv6
            log Warning "Disabling IPv6."
          fi
        ;;
        mixed)
          log Info "Using Mixed: tcp(redirect) + udp(tun)."
          log Info "Creating iptables transparent proxy rules."

          iptables="$IPV"
          probe_tun_device || log Error "tun device: (${tun_device}) not found"
          forward -I || forward -D >/dev/null 2>&1

          if start_redirect; then
            log Info "Creating iptables transparent proxy rules done."
          else
            log Error "Creating iptables transparent proxy rule failed."
            stop_redirect >/dev/null 2>&1
          fi

          if [ "${ipv6}" = "true" ]; then
            log Debug "Using IPv6."
            ipv6_enable
            iptables="$IP6V"
            forward -I || forward -D >/dev/null 2>&1
            if start_redirect; then
              log Info "Creating ip6tables transparent proxy rules done."
            else
              log Error "Creating ip6tables transparent proxy rule failed."
              stop_redirect >/dev/null 2>&1
            fi
          else
            disable_ipv6
            log Warning "Disabling IPv6."
          fi
        ;;
        enhance)
          log Info "Using Enhance: tcp(redirect) + udp(tproxy)"
          log Info "Creating iptables transparent proxy rules."

          iptables="$IPV"
          if start_redirect && start_tproxy; then
            log Info "Creating iptables transparent proxy rules done."
          else
            log Error "Creating iptables transparent proxy rule failed."
            stop_redirect >/dev/null 2>&1
          fi

          if [ "${ipv6}" = "true" ]; then
            log Debug "Using IPv6."
            ipv6_enable
            iptables="$IP6V"
            if start_redirect && start_tproxy; then
              log Info "Creating ip6tables transparent proxy rules done."
            else
              log Error "Creating ip6tables transparent proxy rule failed."
              stop_redirect >/dev/null 2>&1
            fi
          else
            disable_ipv6
            log Warning "Disabling IPv6."
          fi
        ;;
        *)
          log Error "network_mode: ${network_mode}, unknown"
          exit 1
        ;;
      esac
      [ $1 = "renew" ] && log Debug "restart iptables transparent proxy rules done."
      bin_alive && log Info "${bin_name} connected."
      ;;
    disable)
      ipv6_enable
      probe_user_group || log Error "Failed to check BOX user group. Please ensure ${bin_name} kernel is started."
      log Warning "Cleaning up iptables transparent proxy rules."

      cleanup_iptables

      log Warning "Cleaning up iptables transparent proxy rules done."
      ;;
    help|-h|--help|"")
      echo "Usage: $0 {enable|disable|renew}"
      echo
      echo "Commands:"
      echo "  enable   - Enable iptables rules"
      echo "  disable  - Disable iptables rules"
      echo "  renew    - Reapply or refresh iptables rules"
      echo
      echo "Example:"
      echo "  $0 enable"
      ;;
    *)
      echo "$0: '$1' not found"
      echo "Run '$0 help' for usage."
      ;;
  esac
else
  case "$1" in
    enable|renew)
      box_etc
      log Info "$IPV + $IP6V"
      log Info "Using Tun: tcp + udp."

      probe_user_group || {
        log Error "Failed to check BOX user group. Please ensure ${bin_name} kernel is started."
      }

      cleanup_iptables
      probe_tun_device || log Error "tun device: (${tun_device}) not found"
      [ $1 = "renew" ] && log Warning "Cleaning up tun rules."
      iptables="$IPV"

      [ -n "${packages_list}" ] && log Debug "proxy mode: $proxy_mode (${packages_list[*]})"

      if forward -I; then
        log Info "Create iptables tun rules done."
      else
        log Error "Create iptables tun rules failed."
        forward -D >/dev/null 2>&1
      fi

      if [ "${ipv6}" = "true" ]; then
        log Debug "Using IPv6."
        ipv6_enable
        iptables="$IP6V"
        if forward -I; then
          log Info "Create ip6tables tun rules done."
        else
          log Error "Create ip6tables tun rules failed."
          forward -D >/dev/null 2>&1
        fi
      else
        disable_ipv6
        log Warning "Disable IPv6."
      fi
      [ $1 = "renew" ] && log Info "Restart iptables tun rules done."
      bin_alive && log Info "${bin_name} connected."
      ;;
    disable)
      ipv6_enable
      probe_user_group || log Error "Failed to check BOX user group. Please ensure ${bin_name} kernel is started."
      log Warning "Cleaning up tun rules."

      cleanup_iptables

      log Warning "Cleaning up tun rules done."
      ;;
    help|-h|--help|"")
      echo "Usage: $0 {enable|disable|renew}"
      echo
      echo "Commands:"
      echo "  enable   - Enable iptables rules"
      echo "  disable  - Disable iptables rules"
      echo "  renew    - Reapply or refresh iptables rules"
      echo
      echo "Example:"
      echo "  $0 enable"
      ;;
    *)
      echo "$0: '$1' not found"
      echo "Run '$0 help' for usage."
      ;;
  esac
fi
